import json
import os
import random
import shutil
import time

import numpy as np
import ddddocr
import requests
from PIL import Image
from loguru import logger
dir_path = os.path.dirname(os.path.abspath(__file__))

class GTrace(object):
    def __init__(self):
        self.__pos_x = []
        self.__pos_y = []
        self.__pos_z = []

    def __set_pt_time(self):
        """
        设置各节点的时间
        分析不同时间间隔中X坐标数量的占比
        统计结果: 1. 80%~90%的X坐标在15~20毫秒之间
                2. 10%~15%在20~200及以上，其中 [-a, 0, x, ...] 这里x只有一个，取值在110~200之间
                    坐标集最后3~5个坐标取值再50~400之间，最后一个坐标数值最大

        滑动总时间的取值规则: 图片宽度260，去掉滑块的宽度剩下200;
                        如果距离小于100，则耗时1300~1900之间
                        如果距离大于100，则耗时1700~2100之间
        """
        __end_pt_time = []
        __move_pt_time = []
        self.__pos_z = []

        total_move_time = self.__need_time * random.uniform(0.8, 0.9)
        start_point_time = random.uniform(110, 200)
        __start_pt_time = [0, 0, int(start_point_time)]

        sum_move_time = 0

        _tmp_total_move_time = total_move_time
        while True:
            delta_time = random.uniform(15, 20)
            if _tmp_total_move_time < delta_time:
                break

            sum_move_time += delta_time
            _tmp_total_move_time -= delta_time
            __move_pt_time.append(int(start_point_time+sum_move_time))

        last_pt_time = __move_pt_time[-1]
        __move_pt_time.append(last_pt_time+_tmp_total_move_time)

        sum_end_time = start_point_time + total_move_time
        other_point_time = self.__need_time - sum_end_time
        end_first_ptime = other_point_time / 2

        while True:
            delta_time = random.uniform(110, 200)
            if end_first_ptime - delta_time <= 0:
                break

            end_first_ptime -= delta_time
            sum_end_time += delta_time
            __end_pt_time.append(int(sum_end_time))

        __end_pt_time.append(int(sum_end_time + (other_point_time/2 + end_first_ptime)))
        self.__pos_z.extend(__start_pt_time)
        self.__pos_z.extend(__move_pt_time)
        self.__pos_z.extend(__end_pt_time)

    def __set_distance(self, _dist):
        """
        设置要生成的轨迹长度
        """
        self.__distance = _dist

        if _dist < 100:
            self.__need_time = int(random.uniform(500, 1500))
        else:
            self.__need_time = int(random.uniform(1000, 2000))

    def __get_pos_z(self):
        return self.__pos_z

    def __get_pos_y(self):
        _pos_y = [random.uniform(-40, -18), 0]
        point_count = len(self.__pos_z)
        x = np.linspace(-10, 15, point_count - len(_pos_y))
        arct_y = np.arctan(x)

        for _, val in enumerate(arct_y):
            _pos_y.append(val)

        return _pos_y

    def __get_pos_x(self, _distance):
        """
        绘制标准的数学函数图像: 以 tanh 开始 以 arctan 结尾
        根据此模型用等比时间差生成X坐标
        """
        # first_val = random.uniform(-40, -18)
        # _distance += first_val
        _pos_x = [random.uniform(-40, -18), 0]
        self.__set_distance(_distance)
        self.__set_pt_time()

        point_count = len(self.__pos_z)
        x = np.linspace(-1, 19, point_count-len(_pos_x))
        ss = np.arctan(x)
        th = np.tanh(x)

        for idx in range(0, len(th)):
            if th[idx] < ss[idx]:
                th[idx] = ss[idx]

        th += 1
        th *= (_distance / 2.5)

        i = 0
        start_idx = int(point_count/10)
        end_idx = int(point_count/50)
        delta_pt = abs(np.random.normal(scale=1.1, size=point_count-start_idx-end_idx))
        for idx in range(start_idx, point_count):
            if idx*1.3 > len(delta_pt):
                break

            th[idx] += delta_pt[i]
            i+=1

        _pos_x.extend(th)
        return _pos_x[-1], _pos_x

    def get_mouse_pos_path(self, distance):
        """
        获取滑动滑块鼠标的滑动轨迹坐标集合
        """
        result = []
        _distance, x = self.__get_pos_x(distance)
        y = self.__get_pos_y()
        z = self.__get_pos_z()

        for idx in range(len(x)):
            result.append([int(x[idx]), int(y[idx]), int(z[idx])])

        return int(_distance), result


class Geetest(object):
    def __init__(self):
        self.sess = requests.session()
        self.sess.headers = {
            # 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.37',
            'User-Agent': 'Mozilla/5.0 (Linux; Android 12; ANG-AN00 Build/HUAWEIANG-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.0.0 Mobile Safari/537.36 XWEB/1160055 MMWEBSDK/20231201 MMWEBID/9720 MicroMessenger/8.0.45.2521(0x28002D3D) WeChat/arm64 Weixin NetType/5G Language/zh_CN ABI/arm64',
            'Referer': 'https://www.geetest.com/show',
            'Accept-Language': 'zh-CN,zh;q=0.9',
            'Accept': 'application/json, text/plain, */*'
        }
        self.gt = ''
        self.challenge = ''
        self.c = ''
        self.s = ''
        self.token = None

    def get_t(self):
        return int(time.time() * 10000)

    def get_gcl(self):
        url = 'https://www.geetest.com/demo/gt/register-slide-official?t={}'.format(self.get_t())
        # url = 'https://www.geetest.com/demo/gt/register-slide?t={}'.format(self.get_t())
        response = self.sess.get(url).json()
        self.gt = response['gt']
        self.challenge = response['challenge']
        logger.info(f"1.1 register_slide获取gt: {self.gt}  challenge: {self.challenge}")

    def get_script(self):
        url = 'https://apiv6.geetest.com/gettype.php?gt={}&callback=geetest_{}'.format(self.gt, self.get_t())
        response = self.sess.get(url).text
        data = self.getJson(response)['data']
        fullpage = data['fullpage']
        slide = data['slide']
        logger.info(f'1.2 gettype获取js版本，fullpage: {fullpage}  slide: {slide}')

    def get_c_s(self):
        url = 'https://apiv6.geetest.com/get.php?gt={}&challenge={}&lang=zh-cn&pt=0&client_type=web&callback=geetest_{}'.format(self.gt, self.challenge, self.get_t())
        response = self.sess.get(url).text
        print(response)
        data = self.getJson(response)['data']
        c = data['c']
        s = data['s']
        logger.info(f'1.3 get.php获取c: {c}   s: {s}')  # c: [12, 58, 98, 36, 43, 95, 62, 15, 12]   s: 2a6e744e

    def get_type(self):
        url = 'https://api.geetest.com/ajax.php?gt={}&challenge={}&lang=zh-cn&pt=0&client_type=web&callback=geetest_{}'.format(self.gt, self.challenge, self.get_t())
        response = self.sess.get(url).text
        data = self.getJson(response)
        logger.info(f'2.1 ajax.php获取 {data}')  # {'status': 'success', 'data': {'result': 'slide'}}

    # 获取缺口部分
    def get_x(self, newBg, newFullBg):
        slide = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
        with open(os.path.join(dir_path, newBg), 'rb') as f:
            target_bytes = f.read()
        with open(os.path.join(dir_path, newFullBg), 'rb') as f:
            background_bytes = f.read()

        res = slide.slide_comparison(target_bytes, background_bytes)
        return res['target'][0]

    def spliceImg(self, imagePath, savePath):
        img = Image.open(imagePath)
        # 刚才的数组扣过来
        IMG_SHUFFLE_ORDER = [
            39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12,
            13, 23,
            22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17,
        ]
        # 图片会被切分为 10x80 的小图
        IMG_SHUFFLE_X_STEP = 10
        IMG_SHUFFLE_Y_STEP = 80

        # 最后图片的宽高
        IMG_WIDTH = 260
        IMG_HEIGHT = 160
        # 创建一个图对象片
        newImg = Image.new('RGB', (IMG_WIDTH, IMG_HEIGHT))
        # 按照顺序循环 52 次
        for i in range(len(IMG_SHUFFLE_ORDER)):
            x = IMG_SHUFFLE_ORDER[i] % 26 * 12 + 1
            y = IMG_SHUFFLE_Y_STEP if IMG_SHUFFLE_ORDER[i] > 25 else 0
            # 根据刚才 JS 的逻辑，把图片裁剪出一小块儿
            cut = img.crop((x, y, x + IMG_SHUFFLE_X_STEP, y + IMG_SHUFFLE_Y_STEP))
            # 根据刚才的逻辑，确定新图片的位置
            newX = i % 26 * 10
            newY = IMG_SHUFFLE_Y_STEP if i > 25 else 0
            # 把新图片拼接过去
            newImg.paste(cut, (newX, newY))
        newImg.save(savePath)

    def get_img_data(self, url, name):
        request = requests.get(url)
        with open(name, 'wb+') as f:
            f.write(request.content)
        f.close()

    def getJson(self, response):
        return json.loads(response[23:-1])

    import random
    def __ease_out_expo(self, sep):
        '''
            轨迹相关操作
        '''
        if sep == 1:
            return 1
        else:
            return 1 - pow(2, -10 * sep)

    def get_slide_track(self, distance):
        """
        根据滑动距离生成滑动轨迹
        :param distance: 需要滑动的距离
        :return: 滑动轨迹<type 'list'>: [[x,y,t], ...]
            x: 已滑动的横向距离
            y: 已滑动的纵向距离, 除起点外, 均为0
            t: 滑动过程消耗的时间, 单位: 毫秒
        """

        if not isinstance(distance, int) or distance < 0:
            raise ValueError(f"distance类型必须是大于等于0的整数: distance: {distance}, type: {type(distance)}")
        # 初始化轨迹列表
        slide_track = [
            [random.randint(-50, -10), random.randint(-50, -10), 0],
            [0, 0, 0],
        ]
        # 共记录count次滑块位置信息
        count = 40 + int(distance / 2)
        # 初始化滑动时间
        t = random.randint(50, 100)
        # 记录上一次滑动的距离
        _x = 0
        _y = 0
        for i in range(count):
            # 已滑动的横向距离
            x = round(self.__ease_out_expo(i / count) * distance)
            # y = round(__ease_out_expo(i / count) * 14)
            # 滑动过程消耗的时间
            t += random.randint(10, 50)
            if x == _x:
                continue
            slide_track.append([x, _y, t])
            _x = x
        slide_track.append(slide_track[-1])
        return slide_track

    def get_bg(self):
        url = 'https://api.geetest.com/get.php?is_next=true&type=slide3&gt={}&challenge={' \
              '}&lang=zh-cn&https=true&protocol=https%3A%2F%2F&offline=false&product=embed&api_server=api.geetest.com' \
              '&isPC=true&autoReset=true&width=100%25&callback=geetest_{}'.format(self.gt, self.challenge, self.get_t())
        response = self.sess.get(url).text
        print(response)
        data = self.getJson(response)
        self.challenge = data['challenge']
        self.c = data['c']
        self.s = data['s']
        logger.info(f'2.2 get.php获取 {data}')
        slice_big_img = 'https://static.geetest.com/' + data['bg']
        slice_full_img = 'https://static.geetest.com/' + data['fullbg']
        slice_small_img = 'https://static.geetest.com/' + data['slice']
        if os.path.isdir('pics') is False:
            os.mkdir('pics')
        times = 'pics/' + str(time.time_ns())
        if os.path.isdir(times) is False:
            os.mkdir(times)
        savePicName = {
            'bg': times + '/bg.png',
            'slice': times + '/slice.png',
            'fullbg': times + '/fullbg.png',
            'new-bg': times + '/new_bg.png',
            'new-fullbg': times + '/new_fullbg.png',
        }
        self.get_img_data(slice_big_img, savePicName['bg'])
        self.get_img_data(slice_small_img, savePicName['slice'])
        self.get_img_data(slice_full_img, savePicName['fullbg'])
        self.spliceImg(savePicName['bg'], savePicName['new-bg'])
        self.spliceImg(savePicName['fullbg'], savePicName['new-fullbg'])
        result_x = self.get_x(savePicName['new-bg'], savePicName['new-fullbg'])
        # 处理后，把图片删除掉
        shutil.rmtree(times)
        print('new x', result_x)
        gtrace = GTrace()
        # -10，图片不是从头部开始的
        distance, track = gtrace.get_mouse_pos_path(result_x - 10)
        url = 'http://127.0.0.1:8090/getw'
        result_x = track[-1][0]
        data = {
            'x': result_x,
            'c': json.dumps(self.c),
            's': self.s,
            'gt': self.gt,
            'challenge': self.challenge,
            'passtime': track[-1][2],
            'track': json.dumps(track)
        }
        print(data)
        res = requests.post(url, data=data).text
        self.submit(res)


    def submit(self, w):
        url = 'https://api.geetest.com/ajax.php?gt={}&challenge={}&lang=zh-cn&$_BCN=0&client_type=web&w={' \
              '}&callback=geetest_{}'.format(self.gt, self.challenge, w, self.get_t())
        response = self.sess.get(url).text
        data = self.getJson(response)
        logger.info(f'2.3 ajax.php提交 {data}')

if __name__ == '__main__':
    gee = Geetest()
    gee.get_gcl()
    gee.get_script()
    gee.get_c_s()
    gee.get_type()
    gee.get_bg()